#ifndef NETWORK
#define NETWORK

#include <cstddef>
#include <stdint.h>
#include <boost/thread.hpp>
#include <deque>

class NodeEnvironmentInterface;
class DataSourceInterface;
class DataSinkInterface;

// TODO alloc of some sort

class Network {
	public:
		Network(int argc, char** argv);
		~Network();

		void startNetworkThread();
		void stopNetworkThread();

		uint32_t getThreadId() const;
		void setThreadId(uint32_t threadId_);

		// THREAD COMMUNICATION
		// Must be used after startNetworkThread()
		// Reads some data from network for current thread
		char* read(std::size_t* data_len, char* flag);
		// Writes data to given node in next layer (with opposite (1/0) threadId)
		void send(uint32_t node, const char* data, std::size_t data_len, char flag);

		// EXPLICIT THREAD COMMUNICATION
		// Use with care, must be used after startNetworkThread()
		// Reads some data from network for given layer (thread)
		char* readAsThread(uint32_t asThreadId, std::size_t* data_len, char* flag);
		// Writes data to given node to given layer (thread, 0/1)
		void sendToThread(uint32_t node, uint32_t toThreadId, const char* data, std::size_t data_len, char flag);

		uint32_t my_node_number() const;
		uint32_t nodes_count() const;

		// RAW COMMUNICATION between machines
		// Should not be used after startNetworkThread()
		// Reads some data from network for current worker (machine)
		char* readRaw(std::size_t* data_len);
		// Sends data to given node treated as a worker (machine)
		void sendRaw(uint32_t node, const char* data, std::size_t data_len);

		DataSourceInterface* openDataSourceFile(int source_file_id);
		DataSinkInterface* openDataSink();

	private:
		boost::mutex bufferMutex;
		boost::condition_variable dataReadyForThreadCond[2];
		// TODO mutex for writing to the same worker

		NodeEnvironmentInterface* nei;
		boost::thread_specific_ptr<uint32_t> threadId;

		struct Packet {
			char* data;
      std::size_t length;
			char flag;
		};

		std::deque<Packet> buffer[2];
		
		boost::thread* networkThread;
		
		friend void networkThreadFun();
};

#endif
